/* ********************************************************** */
// Test: Run a battleship game

import edu.upenn.seas.peerreview.{Id, Source, Sink}
import edu.upenn.seas.peerreview.transport.Transport
import edu.upenn.seas.peerreview.transport.Transport._
import edu.upenn.seas.peerreview.smui.{Battleship, SmType, AppType, GameConfig}
import edu.upenn.seas.peerreview.pr._
import edu.upenn.seas.peerreview.Debug.debugLevel

object Tester {
  
  // type TEL = PRLayer // switches in different implementations
  type TEL = PRLayer
  def wrapWithPR (myId:Id, transport:Sink.T, f:Source.Factory) : Source.T = {
    new TEL(myId, transport, f)
  }
  
 def wrapWithCheatingPR(myId:Id, transport:Sink.T, f:Source.Factory) : Source.T = {
    new PRSwitching(myId, transport, f)
  }

  def wrapWithSimple (myId: Id, transport: Sink.T, f: Source.Factory) : Source.T = {
    new PRSimple(myId, transport, f)
  }

  def main(argarr: Array[String]):Unit = {

    val sethIP = "165.123.132.214" // don't change this, use -s instead
    val sethPort = 9900

    val usageStr = List("Usage:"
      ,"java -jar PRBS.jar [-h] [-d DEBUGFLAG]* [-t TRANSPORTTYPE] [-c] [-cpr] [-n PLAYERNAME] [-s SERVERIP] [-p PORT] [-m MSGMODE] [--numplayers NUMPLAYERS] APPTYPE"
      ,""
      ,"c             = CHEAT (always miss)"
      ,"cpr           = CHEATING PR"
      ,"DEBUGFLAG     = see Common.scala for channels"
      ,"TRANSPORTTYPE = local | client | server         (default = local)"
      ,"   local:   run coordinators and all players locally"
      ,"   client:  run one player on remote transport"
      ,"   server:  run server + coordinators on remote transport"
      ,"APPTYPE       = console | gui | bot    (default = bot)" 
      ,"PLAYERNAME    = player name, if console"
      ,"SERVERIP      = address of sever, if client     (default = %s)".format(sethIP)
      ,"PORT          = port, if client or server       (default = %d)".format(sethPort)
      ,"MSGMODE       = explicit | rpc          (default = explicit)"
      ,""
      ,"NUMPLAYERS    = number of players in tournament (default = 3)"
    ).mkString("\n")

    def usageError(errorstr: String): Nothing = {
      println(errorstr+"\n"+usageStr)
      exit(1)
    }

    //loop to parse args
    var args=argarr.toList
    var oldLength=0
    var playerName="Client"
    var transporttype = 'local
    var servIP = sethIP
    var port = sethPort
    var msgmode = 'explicit
    var tournnumplayers = 3
    var roomName = "Room1"
    var cheat: Boolean = false;
    var cheatpr: Boolean = false
    var game: Int = 1

    //arg parse loop
    while (args.length >=1 && args.length != oldLength) {
      oldLength=args.length
      args = args match {
        case helpopt :: rest if (helpopt == "-h" || helpopt == "--help") => 
        usageError("")
        case "-d" :: arg :: rest => { //add on a debug flag
          debugLevel = arg :: debugLevel
          rest
        }
        case "-c" :: rest => {
          cheat = true
          rest
        }
        case "-cpr" :: rest => {
          cheatpr = true
          rest
        }
        
        case "--game" :: arg :: rest => {
          game = arg.toInt
          rest
        }
        
        case "-n" :: arg :: rest => {
          playerName = arg
          rest
        }
        case "-t" :: arg :: rest => { //set the transport type 
          val argl = Symbol(arg.toLowerCase)
          if (List('local,'client,'server).contains(argl))
            transporttype = argl
          rest
        }
        case "-m" :: arg :: rest => { //which type of message to use
          val argl = Symbol(arg.toLowerCase) 
          if (List('explicit,'actor,'rpc).contains(argl))
            msgmode = argl
          rest
        }
        case "-s" :: arg :: rest => {
          servIP = arg
          rest
        }
        case "-p" :: arg :: rest => {
          port = arg.toInt
          rest
        }
        case "--numplayers" :: arg :: rest => {
          tournnumplayers = arg.toInt
          rest
        }
        case "--room" :: arg :: rest => {
          roomName = arg
          rest
        }
        case invalidopt :: rest if (invalidopt(0) == '-') => 
          usageError("Invalid option %s".format(invalidopt))
        case x => x
      }
    }    

    val customType : AppType = (if (args.length > 0) args(0) else "") match {
      case "console" => AppType.Console
      case "gui" => AppType.Gui
      case _ => AppType.DecentBot
    }

    val runServer = transporttype == 'server
    val runClient = transporttype == 'server || transporttype == 'client
    val makeCoordinators = transporttype == 'local || transporttype == 'server
    val maxNumPlayers = transporttype match {
      case 'local => 99999999
      case 'client => 1
      case 'server => 0
    }
    val useRPC = msgmode == 'rpc

    if (runServer) {
      println("running server on %s:%d...".format(java.net.InetAddress.getLocalHost, port))
      Transport.newRemoteServer(port)
      servIP = "localhost"
    }

    val trans : Transport = runClient match {
      case true  => {
        println("connecting client to %s:%d...".format(servIP, port))
        Transport.newRemoteClient(servIP, port)
      }
      case false => Transport.newLocal()
    }

    // List of tournaments.
    var games: List[GameConfig] = List()
    if (game == 2)
      games ::= GameConfig((3, 3), List(3))
    else if (game == 3)
      games ::= GameConfig((2, 2), List(1))
    else
      games ::= GameConfig((4, 3), List(2,3))

    games = games.reverse // So the order of the games is as listed above.

    if (makeCoordinators)
      trans.register (coordinatorName,  Battleship.createNameServer(useRPC) _)

    if (makeCoordinators) {
      trans.register("Manager", (id, t) => 
      wrapWithSimple(id, t, 
          Battleship.createManager(roomName)(games)(tournnumplayers)(useRPC) _))
    }

    for (i <- 1 to Math.min(tournnumplayers, maxNumPlayers)) {
      val cheater = cheat //i == 100
      val smname = if (cheater) (playerName + "!") else playerName
      val smkind = if (cheater) SmType.AlwaysMiss else SmType.Honest
        
      val uikind = if (i == 1) customType else AppType.DecentBot
      val sm = if (i==1 && cheatpr) {
        trans.register(smname+"!", (id,t) => 
        wrapWithCheatingPR(id, t, Battleship.createStateMachine(roomName)(smkind)(useRPC) _))
      }
      else {
        trans.register(smname, (id,t) => 
        wrapWithPR(id, t, Battleship.createStateMachine(roomName)(smkind)(useRPC) _))
      }
      val ui = trans.register("UI", (id, t) => 
        wrapWithSimple(id, t, Battleship.createApplication(sm)(uikind)(useRPC)(i) _))
      0
    }
  }
}
